﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using LeakView.common;
using LeakView.service;
using log4net;
using Newtonsoft.Json;

namespace LeakView.view
{
    public partial class DebugChart : UserControl
    {

        private TimeSpan maxTimeSpan = TimeSpan.FromMinutes(10);

        private DebugFetcher2 fetcher = null;

        private ChartDataSource datasource = new ChartDataSource();

        private List<KeyValuePair<DateTime, double>> waitAdd = new List<KeyValuePair<DateTime, double>>();

        public DebugChart()
        {
            InitializeComponent();

            if (!CSharpUtilities.IsDesignMode())
            {
                var chartArea = this.chart1.ChartAreas.First();
                chartArea.AxisX.Interval = 2D;
                chartArea.AxisX.IntervalType = System.Windows.Forms.DataVisualization.Charting.DateTimeIntervalType.Minutes;
                chartArea.AxisX.LabelStyle.Format = "HH:mm";
            }
 
        }

        public void Start(bool isHeadOrTail)
        {
            Stop();

            fetcher = new DebugFetcher2(this, isHeadOrTail);
        }

        public void Stop()
        {
            if(fetcher != null)
            {
                fetcher.stop();
                fetcher = null;
            }
        }

        public void reset()
        {

            this.chart1.Series.First().Points.Clear();

            var time = DateTime.FromOADate(0);
            //addValue(time, 70D);
            for (int i = 0; i < 1200; i++)
            {
                time = time + TimeSpan.FromSeconds(1);
                addValue(time, double.NaN);
            }

        }
        
        private void addValue(DateTime dateTime, double value)
        {
            lock(waitAdd)
            {
                waitAdd.Add(new KeyValuePair<DateTime, double>(dateTime, value));
            }
        }

        public void dispatchValue(DebugValue value)
        {
            addValue(Tools.ConvertLongToDateTime(value.time), value.measured);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            var temp = new List<KeyValuePair<DateTime, double>>();
            lock(waitAdd)
            {
                temp.AddRange(waitAdd);
                waitAdd.Clear();
            }
            
            if(temp.Count > 0)
            {
                temp.ForEach(pair =>
                {
                    datasource.Add(pair.Key, pair.Value);
                });

                datasource.filterSpan(maxTimeSpan).Bind(this.chart1);

            }

        }

        private void chart1_DoubleClick(object sender, EventArgs e)
        {
#if DEBUG
            
            Random x = new Random();
            addValue(DateTime.Now, GetDemo());
#endif
        }

#if DEBUG

        private Random demoRandom = new Random();
        private int demoCount = 0;
        private double GetDemo()
        {
            demoCount++;

            if((demoCount % 5) == 0)
            {
                return double.NaN;
            }
            else
            {
                bool control = ((Control.ModifierKeys & Keys.Control) == Keys.Control);
                double v = demoRandom.NextDouble();
                if (control) v *= 19792231000;
                return v;
            }

        }

#endif
    }

    public class DebugFetcher2
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(DebugFetcher2));

        private DebugChart chart = null;

        private Thread thread = null;

        private bool exit = false;

        private bool isHeadOrTail = true;

        private string host;
        private int port;

        public DebugFetcher2(DebugChart chart, bool isHeadOrTail)
        {
            this.chart = chart;
            this.isHeadOrTail = isHeadOrTail;
            start();
        }

        public void start()
        {
            this.chart.reset();

            exit = false;
            thread = new Thread(() => threadHandler());
            thread.Start();

        }

        public void stop()
        {
            if (thread != null)
            {
                exit = true;

                this.chart = null;

                thread = null;
            }
        }

        private bool IntializeHostAndPort()
        {
#if DEBUG
            this.host = "127.0.0.1";
            this.port = isHeadOrTail ? 6788 : 6789;
            return true;
#else

            if (!MainModule.pair.Value.Valid)
            {
                log.Info("open debug fail because pair data is invalid.");
                return false;
            }

            AjaxLeakPair pair = MainModule.pair.Value.Value;

            AnyConfig config = AnyConfigs.get("DEBUG");

            if(this.isHeadOrTail)
            {
                string headHost = hostFromUrl(pair.head.url);

                Any aHeadPort = config.get("head_port");
                int portHead = aHeadPort.GetOrDefault(6789);

                if(headHost != null)
                {
                    this.host = headHost;
                    this.port = portHead;
                }
                
            }
            else
            {
                string tailHost = hostFromUrl(pair.tail.url);

                Any aTailPort = config.get("tail_port");
                int portTail = aTailPort.GetOrDefault(6789);

                if (tailHost != null)
                {
                    this.host = tailHost;
                    this.port = portTail;
                }
            }

            return true;

#endif

        }

        private string hostFromUrl(string urlString)
        {
            Uri uri = new Uri(urlString);
            return uri.Host;
        }

        private void threadHandler()
        {
            while (!exit)
            {

                while(!exit)
                {
                    if (this.IntializeHostAndPort())
                    {
                        break;
                    }
                    else
                    {
                        Thread.Sleep(2000);
                    } 
                }

                TcpClient tcp = null;

                try
                {
                    /*
                    tcp = new TcpClient();
                    IAsyncResult connResult = tcp.BeginConnect(host, port, null, null);
                    connResult.AsyncWaitHandle.WaitOne(2000, true);
                    */
                    tcp = NetTools.BuildTcpClient(host, port, 2000);
                    tcp.ReceiveTimeout = 2000;

                    while (!exit && tcp != null && tcp.Connected)
                    {
                        try
                        {
                            NetworkStream ns = tcp.GetStream();
                            ns.WriteByte(1);

                            StreamReader sr = new StreamReader(ns, Encoding.UTF8);

                            string readString = sr.ReadLine();

                            DebugValue value = JsonConvert.DeserializeObject<DebugValue>(readString);

                            this.chart?.dispatchValue(value);

                        }
                        catch (Exception e)
                        {
                            if(tcp.Connected)
                            {
                                log.Info("Read value from node timeout.");
                                continue;
                            }
                            else
                            {
                                log.Error("Read and dispatch value from node failed.", e);
                                break;
                            }
                        }

                    }

                }
                catch (Exception e)
                {
                    log.Warn("Connect to node failed.", e);
                }
                finally
                {
                    if (tcp != null)
                    {
                        tcp.Close();
                        tcp.Dispose();
                        tcp = null;
                    }
                }

                if (!exit)
                {
                    Thread.Sleep(2000);
                }

            }


        }


    }

    public class DebugValue
    {
        public long time { get; set; }
        public double measured { get; set; }
    }
}
